/*
 * batch_main.cpp
 *
 *  Created on: Feb 28, 2011
 *      Author: tqlong
 */

#include <boost/program_options.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/vector_sparse.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <armadillo>

template<typename B>
class uBlasVec : public B
{
//  typedef boost::numeric::ublas::vector<double> __Base;
  typedef B __Base;
public:
  unsigned int n_elem;
  uBlasVec(int d = 1) : __Base(d), n_elem(d) {}
  template <typename E>
  const uBlasVec& operator = (const boost::numeric::ublas::vector_expression<E>& e) {
    __Base::operator = (e);
    return *this;
  }
  const uBlasVec& randn() {
    for (unsigned int i = 0; i < __Base::size(); i++)
      (*this)(i) = arma::randn(1)[0];
    return *this;
  }
  const uBlasVec& randu() {
    for (unsigned int i = 0; i < __Base::size(); i++)
      (*this)(i) = arma::randu(1)[0];
    return *this;
  }
  const uBlasVec& zeros() {
    for (unsigned int i = 0; i < __Base::size(); i++)
      (*this)(i) = 0.0;
    return *this;
  }
  const uBlasVec& ones() {
    for (unsigned int i = 0; i < __Base::size(); i++)
      (*this)(i) = 1.0;
    return *this;
  }
  double get(int i) const { return (*this)(i); }
  double& ref(int i) { return (*this)(i); }
};

namespace arma {
  template<typename E1, typename E2>
  double dot(const boost::numeric::ublas::vector_expression<E1>& u,
             const boost::numeric::ublas::vector_expression<E2>& v)
  { return boost::numeric::ublas::inner_prod(u,v); }
  template<typename E>
  double norm(const boost::numeric::ublas::vector_expression<E>& u, int ell) {
    switch (ell) {
    case 1: return boost::numeric::ublas::norm_1(u);
    case 2: return boost::numeric::ublas::norm_2(u);
    default: return boost::numeric::ublas::norm_inf(u);
    }
  }
  template<typename E>
  double norm(const boost::numeric::ublas::vector_expression<E>& u, const char* ell) {
    if (strcmp(ell, "1") == 0) return boost::numeric::ublas::norm_1(u);
    else if (strcmp(ell, "2") == 0) return boost::numeric::ublas::norm_2(u);
    else return boost::numeric::ublas::norm_inf(u);
  }
}
template<typename S, typename T>
void save(S& s, const uBlasVec<T>& u) {
  s << u.size() << " ";
  for (unsigned int i = 0; i < u.size(); i++)
    s << i << ":" << u(i);
}
template <typename S, typename T>
void load(S& f, uBlasVec<T>& x)
{
  int nnz;
  f >> nnz;
  x.zeros();
  for (int k = 0; k < nnz; k++) {
    unsigned int i;
    double val;
    char c;
    f >> i >> c >> val;
    assert(c == ':' && i < x.n_elem && i >= 0);
//    std::cout << "i = " << i << c << "val = " << val << " ";
    x[i] = val;
  }
}

#include "data/dataset_tpl.h"
#include "learning_tpl.h"

namespace po = boost::program_options;
po::options_description desc("Allowed options");
po::variables_map vm;

void process_options(int argc, char** argv);

void test_ublas();

template <typename vec>
void generateData()
{
  int n = vm["n"].as<int>();
  int d = vm["d"].as<int>();
  Dataset<vec> data;
  data.generate(n, d);
  data.save(vm["data"].as<std::string>());
}

template <typename vec>
void batchLearn()
{
  Dataset<vec> data;
  data.load(vm["data"].as<std::string>());
  std::vector<double> params;
  BatchLearningAlgorithm<vec> *p = NULL;

  if (vm["method"].as<std::string>() == "l1l2") {
    p = new L1LossL2Reg<vec>(data.dim());
    params.push_back(vm["alpha"].as<double>());
    params.push_back(vm["C"].as<double>());
    params.push_back(vm["iter"].as<int>());
  }
  else if (vm["method"].as<std::string>() == "l2l2") {
    p = new L2LossL2Reg<vec>(data.dim());
    params.push_back(vm["alpha"].as<double>());
    params.push_back(vm["C"].as<double>());
    params.push_back(vm["iter"].as<int>());
  }

  if (!p) {
    std::cout << "Unknown algorithm : " << vm["method"].as<std::string>() << "\n";
    return;
  }
  std::cout << "Algorithm : " << vm["method"].as<std::string>() << "\n";
//  std::cout << "start = \n"; p->print(); std::cout << "\n";
  p->setParameters(params);
  double error = p->update(data);
  std::cout << "result = \n"; p->print(); std::cout << "\n";
  for (int i = 0; i < data.n(); i++) {
    double pred = p->predict(data.x(i));
    std::cout << i << " y = " << data.y(i) << " pred = " << pred
              << " --> error = " << (data.y(i)*pred <= 0) << "\n";
  }
  std::cout << "error = " << error << "\n";
  delete p;
}

int main(int argc, char** argv) {
//  test_sparse(); return 0;
//  test_ublas(); return 0;
  process_options(argc, argv);
  if (vm.count("help") || !vm.count("data")) {
    std::cout << desc << "\n";
    return 0;
  }
  if (vm.count("gen")) {
    generateData<uBlasVec<boost::numeric::ublas::vector<double> > >();
    return 0;
  }

  boost::posix_time::ptime time_start(boost::posix_time::microsec_clock::local_time());

  batchLearn <uBlasVec<boost::numeric::ublas::vector<double> > > ();

  boost::posix_time::ptime time_end(boost::posix_time::microsec_clock::local_time());
  boost::posix_time::time_duration duration(time_end - time_start);
  std::cerr << "Duration = " << duration << "\n";
  return 0;
}

void process_options(int argc, char** argv)
{
  desc.add_options()
    ("help", "produce this usage")
    ("data", po::value<std::string>(), "file contains training samples")
    ("sparse", "use sparse vector representation")
    ("gen", "generate random data")
    ("noise", po::value<double>()->default_value(0.2), "learning rate")
    ("n", po::value<int>()->default_value(10), "number of points")
    ("d", po::value<int>()->default_value(2), "input dimension")
    ("iter", po::value<int>()->default_value(10), "maximum number of iterations")
    ("alpha", po::value<double>()->default_value(1.0), "learning rate")
    ("C", po::value<double>()->default_value(100.0), "regularized parameter")
    ("method", po::value<std::string>()->default_value("l1l2"), "learning algorithm: l1l2")
    ;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);
}

void test_ublas()
{
  using namespace boost::numeric::ublas;
  for (int i = 0; i < 3; ++ i) {
      unit_vector<double> v (3, i);
      std::cout << v << std::endl;
  }
}
